package transaction

import (
	"TDD/src/user"
	"sync"
	"testing"
	"github.com/stretchr/testify/require"
)

func TestCheckAmount(t *testing.T) {
    from := user.User{ID: 1, Balance: 1000, Mutex: &sync.Mutex{}}
    to := user.User{ID: 2, Balance: 1000, Mutex: &sync.Mutex{}}
    transaction := Transaction{ID: 1, From: &from, To: &to, Amount: 500.22}

    t.Log("Amount test.")
    {
        testID := 0
        t.Logf("\tTest %d: amount < 0.", testID)
        {
            transaction.Amount = -500.22
            if transaction.CheckAmount() {
                t.Errorf("\t\tExpected false for %f amount", transaction.Amount)
            }
        }

        testID++
        t.Logf("\tTest %d: amount > 0.", testID)
        {
            transaction.Amount = 500.22
            if !transaction.CheckAmount() {
                t.Errorf("\t\tExpected true for %f amount", transaction.Amount)
            }
        }

        testID++
        t.Logf("\tTest %d: amount = 0.", testID)
        {
            transaction.Amount = 0.0
            if transaction.CheckAmount() {
                t.Errorf("\t\tExpected false for zero amount")
            }
        }
    }
}

func TestCheckBalance(t *testing.T) {
    from := user.User{ID: 1, Balance: 1000, Mutex: &sync.Mutex{}}
    to := user.User{ID: 2, Balance: 1000, Mutex: &sync.Mutex{}}
    transaction := Transaction{ID: 1, From: &from, To: &to, Amount: 500.22}

    t.Log("From-user balance test.")
    {
        testID := 0
        t.Logf("\tTest %d: from user balance < 0.", testID)
        {
            from.Balance = -10
            if transaction.CheckBalance() {
                t.Errorf("\t\tExpected false for %f balance", from.Balance)
            }
        }

        testID++
        t.Logf("\tTest %d: from user balance = 0.", testID)
        {
            from.Balance = 0
            if transaction.CheckBalance() {
                t.Errorf("\t\tExpected false for %f user balance", from.Balance)
            }
        }

        testID++
        t.Logf("\tTest %d: from user balance > 0 and balance > amount.", testID)
        {
            from.Balance = 500.50
            transaction.Amount = 500.22
            if !transaction.CheckBalance() {
                t.Errorf("\t\tExpected true for %f balance and %f amount", 
                          from.Balance, transaction.Amount)
            }
        }

        testID++
        t.Logf("\tTest %d: from user balance > 0 and balance < amount.", testID)
        {
            from.Balance = 500.00
            transaction.Amount = 500.22
            if transaction.CheckBalance() {
                t.Errorf("\t\tExpected false for %f balance and %f amount", 
                          from.Balance, transaction.Amount)
            }
        }

        testID++
        t.Logf("\tTest %d: from user balance > 0 and balance = amount.", testID)
        {
            from.Balance = 500.22
            transaction.Amount = 500.22
            if !transaction.CheckBalance() {
                t.Errorf("\t\tExpected false for %f balance and %f amount", 
                          from.Balance, transaction.Amount)
            }
        }
    }
}

func TestConfirmTransaction(t *testing.T) {
    from := user.User{ID: 1, Balance: 100, Mutex: &sync.Mutex{}}
    to := user.User{ID: 2, Balance: 100, Mutex: &sync.Mutex{}}
    transaction := Transaction{ID: 1, From: &from, To: &to, Amount: 500.22}

    t.Log("Confirm transaction test.")
    {
        testID := 0
        t.Logf("\tTest %d: amount > balance.", testID)
        {
            transaction.Amount = 500.22
            from.Balance = 500.23
            to.Balance = 0

            _, err := transaction.ConfirmTransaction()
            require.NoError(t, err)
            require.InDelta(t, from.Balance, 0.01, 0.001)
            require.InDelta(t, to.Balance, 500.22, 0.001)
        }


        testID++
        t.Logf("\tTest %d: amount < balance.", testID)
        {
            transaction.Amount = 1000.22
            from.Balance = 500.23
            to.Balance = 0

            _, err := transaction.ConfirmTransaction()
            require.Error(t, err)
            require.InDelta(t, to.Balance, 0, 0.0001)
        }

        testID++
        t.Logf("\tTest %d: amount = balance.", testID)
        {
            transaction.Amount = 500.22
            from.Balance = 500.22
            to.Balance = 0

            _, err := transaction.ConfirmTransaction()
            require.NoError(t, err)
            require.InDelta(t, to.Balance, 500.22, 0.001)
        }
    }
}

func TestProcessTransactoins(t *testing.T) {
    vadim := user.User{ID: 1, Balance: 300, Mutex: &sync.Mutex{}}
    alex := user.User{ID: 2, Balance: 1000, Mutex: &sync.Mutex{}}
    alis := user.User{ID: 3, Balance: 500.22, Mutex: &sync.Mutex{}}

    transactions := []Transaction {
        {ID: 1, From: &vadim, To: &alex, Amount: 150.34},
        {ID: 2, From: &alis, To: &vadim, Amount: 500.22},
        {ID: 3, From: &vadim, To: &alis, Amount: 10000.23},
        {ID: 4, From: &alex, To: &alis, Amount: -10.27},
        {ID: 5, From: &alis, To: &alex, Amount: 0},
    }

    const bufLen = 10
    transactoinsBuf := make(chan Transaction, bufLen)
    resBuf := make(chan TransactionResult, bufLen)

    var wg sync.WaitGroup

    wg.Add(1)
    go ProcessTransactions(transactoinsBuf, resBuf, &wg)

    go func() {
        for _, ts := range transactions {
            transactoinsBuf <- ts
        }
        close(transactoinsBuf)
    }()

    wg.Wait()
    close(resBuf)

    for res := range resBuf {
        switch res.TransactionID {
        case 1:
            require.True(t, res.Success)
        case 2:
            require.True(t, res.Success)
        case 3:
            require.False(t, res.Success)
        case 4:
            require.False(t, res.Success)
        case 5:
            require.False(t, res.Success)
        }
    }

}
